#include "audio_rec.h"
#include "shared\dbg.h"
#include "avi\avifile.h"

#include <mmsystem.h>


namespace avlib{

int AudioRecorder::buf_cnt = 0; //the buffer count feed to waveIn device

AudioRecorder::AudioRecorder(){
	mhWaveIn = 0;

	//default setting
	mFormat.wFormatTag=WAVE_FORMAT_PCM;
	mFormat.wBitsPerSample = 16;
	mFormat.nChannels=2;
	mFormat.nSamplesPerSec=44100;

	mFormat.nBlockAlign=mFormat.wBitsPerSample/8*mFormat.nChannels;  
	mFormat.nAvgBytesPerSec=mFormat.nSamplesPerSec*mFormat.nBlockAlign;   // = nSamples
	mFormat.cbSize=0;

	m_avi = NULL;
	mbRecording = false;
}

AudioRecorder::~AudioRecorder(){
	stop();
}


void AudioRecorder::set_param(UINT samplerate,unsigned char samplebits,bool is_stereo){

	mFormat.wFormatTag=WAVE_FORMAT_PCM;
	mFormat.wBitsPerSample = samplebits;
	mFormat.nChannels=is_stereo?2:1;
	mFormat.nSamplesPerSec=samplerate;

	mFormat.nBlockAlign=mFormat.wBitsPerSample/8*mFormat.nChannels;  
	mFormat.nAvgBytesPerSec=mFormat.nSamplesPerSec*mFormat.nBlockAlign;   // = nSamples
	mFormat.cbSize=0;
}


bool AudioRecorder::get_volume(int &volume)
{

	//get mixer device
	UINT id;
	MMRESULT res =mixerGetID((HMIXEROBJ)mhWaveIn,&id,MIXER_OBJECTF_HWAVEIN);
	if(res != MMSYSERR_NOERROR) return false;
		
	HMIXER hMixer;
	//open device
	res = mixerOpen(&hMixer,id,NULL,NULL,MIXER_OBJECTF_MIXER);
	if(res != MMSYSERR_NOERROR) return false;

	//get line id
	MIXERLINE ml;
	ml.cbStruct = sizeof(ml);
	ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
	res = mixerGetLineInfo((HMIXEROBJ) hMixer,&ml,MIXER_OBJECTF_HMIXER|MIXER_GETLINEINFOF_COMPONENTTYPE);
	if(res == MMSYSERR_NOERROR){
		MIXERCONTROL ctrl;
		MIXERLINECONTROLS mlc;
		ZeroMemory(&mlc,sizeof(mlc));
		ZeroMemory(&ctrl,sizeof(ctrl));

		mlc.cControls = 1;
		mlc.cbmxctrl  = sizeof(MIXERCONTROL);
		mlc.cbStruct  = sizeof(MIXERLINECONTROLS);
		mlc.pamxctrl = &ctrl;
		mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
		mlc.dwLineID = ml.dwLineID;
		//get volume control
		res = mixerGetLineControls((HMIXEROBJ)hMixer,&mlc,MIXER_OBJECTF_HMIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE);
		if(res == MMSYSERR_NOERROR){
			MIXERCONTROLDETAILS info;
			MIXERCONTROLDETAILS_SIGNED volStruct;
			ZeroMemory(&info,sizeof(info));
			info.cbStruct = sizeof(info);
			info.dwControlID = ctrl.dwControlID;
			info.cChannels = 1;
			info.cMultipleItems  = 0;
			info.cbDetails = sizeof(volStruct);
			info.paDetails = &volStruct;
			//get volume
			res = mixerGetControlDetails((HMIXEROBJ)hMixer,&info,MIXER_OBJECTF_HMIXER|MIXER_GETCONTROLDETAILSF_VALUE);
			if(res== MMSYSERR_NOERROR){
				volume = volStruct.lValue;
			}
		}
	}
	mixerClose(hMixer);
	
	return (res == MMSYSERR_NOERROR);
}


bool AudioRecorder::set_volume(int volume)
{

	//get mixer device
	UINT id;
	MMRESULT res =mixerGetID((HMIXEROBJ)mhWaveIn,&id,MIXER_OBJECTF_HWAVEIN);
	if(res != MMSYSERR_NOERROR) return false;
		
	HMIXER hMixer;
	//open device
	res = mixerOpen(&hMixer,id,NULL,NULL,MIXER_OBJECTF_MIXER);
	if(res != MMSYSERR_NOERROR) return false;

	//get line id
	MIXERLINE ml;
	ml.cbStruct = sizeof(ml);
	ml.dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
	res = mixerGetLineInfo((HMIXEROBJ) hMixer,&ml,MIXER_OBJECTF_HMIXER|MIXER_GETLINEINFOF_COMPONENTTYPE);
	if(res == MMSYSERR_NOERROR){
		MIXERCONTROL ctrl;
		MIXERLINECONTROLS mlc;
		ZeroMemory(&mlc,sizeof(mlc));
		ZeroMemory(&ctrl,sizeof(ctrl));

		mlc.cControls = 1;
		mlc.cbmxctrl  = sizeof(MIXERCONTROL);
		mlc.cbStruct  = sizeof(MIXERLINECONTROLS);
		mlc.pamxctrl = &ctrl;
		mlc.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
		mlc.dwLineID = ml.dwLineID;
		//get volume control
		res = mixerGetLineControls((HMIXEROBJ)hMixer,&mlc,MIXER_OBJECTF_HMIXER|MIXER_GETLINECONTROLSF_ONEBYTYPE);
		if(res == MMSYSERR_NOERROR){
			MIXERCONTROLDETAILS info;
			MIXERCONTROLDETAILS_SIGNED volStruct;
			ZeroMemory(&info,sizeof(info));
			info.cbStruct = sizeof(info);
			info.dwControlID = ctrl.dwControlID;
			info.cChannels = 1;
			info.cMultipleItems  = 0;
			info.cbDetails = sizeof(volStruct);
			info.paDetails = &volStruct;
			volStruct.lValue = volume;
			//get volume
			res = mixerSetControlDetails((HMIXEROBJ)hMixer,&info,MIXER_OBJECTF_HMIXER|MIXER_GETCONTROLDETAILSF_VALUE);
		}
	}
	mixerClose(hMixer);
	
	return (res == MMSYSERR_NOERROR);
}


/*void CALLBACK waveInProc(HWAVEIN hwi,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)
{
	
	static UINT gSndBufSZ = 0;
	static UINT gSZ = 0;//gSZ the current global buffer data size

	WAVEHDR *pHdr=NULL;
	MMRESULT mRes = 0;
	switch(uMsg)
	{
	case WIM_CLOSE:
		break;
	case WIM_DATA:
		{
			try{
			AVRecorder* corder = (AVRecorder*)dwInstance;
		
			if(gSndBuffer == NULL){
				//gSndBufSZ = pAvi->mFormat.nAvgBytesPerSec/FPS;
				gSndBufSZ = corder->get_audio_format()->nAvgBytesPerSec/corder->get_fps();
				gSndBuffer = (LPBYTE)malloc(gSndBufSZ);
			}

			pHdr = (WAVEHDR*) dwParam1;
			if(WHDR_DONE==(WHDR_DONE &pHdr->dwFlags))
			{
				unsigned int ccp = 0;//ccp:  bytes processed 
				while(pHdr->dwBytesRecorded > ccp){
					int av = min(gSndBufSZ-gSZ,pHdr->dwBytesRecorded-ccp);//free space available
					memcpy(gSndBuffer+gSZ,pHdr->lpData+ccp,av);

					gSZ += av;
					ccp += av;

					if(gSZ == gSndBufSZ){//a second sound ready?
						corder->add_video_frame();
						corder->add_audio_frame((LPVOID)gSndBuffer,gSZ);
						gSZ = 0;
					}
				}

				if(corder->is_recording())
					mRes = waveInAddBuffer(hwi,pHdr,sizeof(WAVEHDR));
				
			}
			}catch(...){
				printf("catch it");
			}
		}
		break;
	case WIM_OPEN:
		break;

	default:
		break;
	}
}
*/
/*the avi write is too slow, so sound will lost when buffer all exhausted*/
/* to avoid sound lost, we should create a queue to buffer the imcoming samples
 buf then the buffers will increase more and more when recording going on,
 so we skip video frames to keep sound as possible as could
 */


void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR lpUser, WAVEHDR *buf, DWORD Reserved) 
{
	 DWORD ThreadId = (DWORD)lpUser;
	
	
	PostThreadMessage(ThreadId, uMsg, (WPARAM)hwi, (LPARAM)buf); 
	

}


DWORD WINAPI WaveInThread(LPVOID arg)
{
	static UINT gSndBufSZ = 0;
	static UINT gSZ = 0;//gSZ the current global buffer data size

	

	MMRESULT mRes = 0;
	MSG		msg;
	AudioRecorder* corder = (AudioRecorder*)arg;
	BOOL bRet;

	while (bRet = GetMessage(&msg,(HWND)0, 0, 0) != 0)
	{
		if(bRet == -1){
			return -1;
		}
		switch (msg.message)
		{
			/* A buffer has been filled by the driver */
			case MM_WIM_DATA:
				{
					 

					WAVEHDR *pHdr = (WAVEHDR*) msg.lParam;
					//log("process buffer %d\n",pHdr->dwUser);
					//HWAVEIN hWaveIn = (HWAVEIN)msg.wParam;

					
					//if(buf_cnt >= MAX_BUFFERS/5){
						//if(corder->add_video_frame());//we skip frames to keep sound 
					
					corder->process_buffer(pHdr );
					//}
					/*if(gSndBuffer == NULL){
						//gSndBufSZ = pAvi->mFormat.nAvgBytesPerSec/FPS;
						gSndBufSZ = corder->get_audio_format()->nAvgBytesPerSec/corder->get_fps();
						gSndBuffer = (LPBYTE)malloc(gSndBufSZ);
					}

					
					if(WHDR_DONE==(WHDR_DONE &pHdr->dwFlags))
					{
						unsigned int ccp = 0;//ccp:  bytes processed 
						while(pHdr->dwBytesRecorded > ccp){
							int av = min(gSndBufSZ-gSZ,pHdr->dwBytesRecorded-ccp);//free space available
							memcpy(gSndBuffer+gSZ,pHdr->lpData+ccp,av);

							gSZ += av;
							ccp += av;

							if(gSZ == gSndBufSZ){//a second sound ready?
								log("\nAdd Frame::blt()\n");
								if(buf_cnt >= 10)
									corder->add_video_frame();
									corder->add_audio_frame((LPVOID)gSndBuffer,gSZ);
								
								gSZ = 0;
							}
						}
					}else{
						log("here");
					}*/
					
					
				}
				break;
			case MM_WIM_OPEN:
				break;
			case MM_WIM_CLOSE:
				
					return 0;
				break;
		}
	}
	return 0;
}


bool AudioRecorder::process_buffer(WAVEHDR *pHdr){

	bool res = true;

	buf_cnt --;
	if(buf_cnt < 0)
	{
		log("audio lost,please increase the buffer size");
		assert(false);
	}
	if(m_avi){
		res = m_avi->AppendSoundSamples(pHdr->lpData,pHdr->dwBytesRecorded);
		//nSample = avi->get_sample_count();
		
	}
	if(mbRecording){
		MMRESULT mRes = waveInAddBuffer(mhWaveIn,pHdr,sizeof(WAVEHDR));
		if(mRes == MMSYSERR_NOERROR)
			buf_cnt++;
	}
	return res;
	
}

/*int AudioRecorder::open(UINT device,DWORD thread){
	if(mhWaveIn != 0) close();

	mDeviceId = device;
	//TODO ,check device capbility if comply  

	//MMRESULT mres = waveInOpen(&mhWaveIn, WAVE_MAPPER,&mFormat,(DWORD_PTR)dwCallback, (DWORD_PTR)param, CALLBACK_FUNCTION);
	MMRESULT mres = waveInOpen(&mhWaveIn, device,&mFormat,(DWORD_PTR)thread, 0, CALLBACK_THREAD);
	if(mres != MMSYSERR_NOERROR){
		char fault[256];
		waveInGetErrorText(mres, fault, 256);
		log("waveInOpen error %d:%s",mres,fault);
		return -1;
	}


	for(int idx=0;idx<MAX_BUFFERS;++idx)
	{
		memset(&mWaveHDR[idx],0,sizeof(WAVEHDR));

		int FPS = 20;
		mWaveHDR[idx].lpData=(LPSTR)HeapAlloc(GetProcessHeap(),8,mFormat.nAvgBytesPerSec/FPS);
		mWaveHDR[idx].dwBufferLength=mFormat.nAvgBytesPerSec/FPS;
		mWaveHDR[idx].dwUser=idx;
		mWaveHDR[idx].dwFlags = WHDR_DONE;

		mres=waveInPrepareHeader(mhWaveIn,&mWaveHDR[idx],sizeof(WAVEHDR));

		//MMSYSERR_BADDEVICEID
		if(mres!=MMSYSERR_NOERROR)
		{
			log("waveInPrepareHeader error %d",mres);
			return -1;
		}
		mres=waveInAddBuffer(mhWaveIn,&mWaveHDR[idx],sizeof(WAVEHDR));
		if(mres!=MMSYSERR_NOERROR)
		{
			log("waveInAddBuffer error %d",mres);
			return -1;
		}
	}
	return 0;
}*/
int AudioRecorder::open(UINT device){
	if(mhWaveIn != 0) stop();

	
	//TODO ,check device capbility if comply  
	DWORD thread_id;
	hWaveInThread = ::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WaveInThread,this,0,&thread_id);
		/*BOOL res = ::SetThreadPriority(hWaveInThread,THREAD_PRIORITY_HIGHEST);
		if(res == 0){
			log("SetThreadPriority error:%d\n",GetLastError());
		}*/

	mDeviceId = device;

	//MMRESULT mres = waveInOpen(&mhWaveIn, WAVE_MAPPER,&mFormat,(DWORD_PTR)dwCallback, (DWORD_PTR)param, CALLBACK_FUNCTION);
	MMRESULT mres = waveInOpen(&mhWaveIn, device,&mFormat,(DWORD_PTR)waveInProc, (DWORD_PTR)thread_id, CALLBACK_FUNCTION);
	if(mres != MMSYSERR_NOERROR){
		char fault[256];
		waveInGetErrorText(mres, fault, 256);
		log("waveInOpen error %d:%s",mres,fault);
		return -1;
	}

	waveInReset(mhWaveIn);

	for(int idx=0;idx<MAX_BUFFERS;++idx)
	{
		memset(&mWaveHDR[idx],0,sizeof(WAVEHDR));

		int FPS = 20;
		mWaveHDR[idx].lpData=(LPSTR)HeapAlloc(GetProcessHeap(),8,mFormat.nAvgBytesPerSec/FPS);
		mWaveHDR[idx].dwBufferLength=mFormat.nAvgBytesPerSec/FPS;
		mWaveHDR[idx].dwUser=idx;
		mWaveHDR[idx].dwFlags = WHDR_DONE;

		mres=waveInPrepareHeader(mhWaveIn,&mWaveHDR[idx],sizeof(WAVEHDR));

		//MMSYSERR_BADDEVICEID
		if(mres!=MMSYSERR_NOERROR)
		{
			log("waveInPrepareHeader error %d",mres);
			return -1;
		}
		mres=waveInAddBuffer(mhWaveIn,&mWaveHDR[idx],sizeof(WAVEHDR));
		if(mres!=MMSYSERR_NOERROR)
		{
			log("waveInAddBuffer error %d",mres);
			return -1;
		}
	}

	buf_cnt = MAX_BUFFERS;
	return 0;
}


bool AudioRecorder::start(AviFile *avi){
	
	m_avi = avi;
	
	MMRESULT mres=waveInStart(mhWaveIn);
	if(mres==MMSYSERR_NOERROR){
		mbRecording = true;	
	}else{
		log("waveInStart eror %d",mres);
		return false;
	}
	return true;
}


int AudioRecorder::stop(){
	mbRecording = false;
	if(mhWaveIn == 0) return 0;
	MMRESULT mres=waveInStop(mhWaveIn);
	if(mres != MMSYSERR_NOERROR ){
		log("waveInStop errr waveInStop %d",mres);
		return -1;
	}

	mres = waveInReset(mhWaveIn);

	int i=0;
	
	//wait for all buffer released
	while(buf_cnt > 0) Sleep(100);

	for(i=0;i<MAX_BUFFERS;++i){
		if(mWaveHDR[i].lpData){
			if((mWaveHDR[i].dwFlags & WHDR_DONE) ==  0) Sleep(100);
			MMRESULT mres=waveInUnprepareHeader(mhWaveIn,&mWaveHDR[i],sizeof(WAVEHDR));

			if(mres != MMSYSERR_NOERROR){
				log("waveInUnprepareHeader err %d",mres);
				return -1;
			}
			HeapFree(GetProcessHeap(),0,mWaveHDR[i].lpData);
			ZeroMemory(&mWaveHDR[i],sizeof(WAVEHDR));
		}
	}

	mres = MMSYSERR_NOERROR;

	
	mres = waveInClose(mhWaveIn);
	if(mres != MMSYSERR_NOERROR){
		log("waveInClose err %d",mres);
		return -1;
	}

	

	if(::WaitForSingleObject(hWaveInThread,500) != WAIT_OBJECT_0){
		::TerminateThread(hWaveInThread,500);
	}
	::CloseHandle(hWaveInThread);
	hWaveInThread = 0;
	mhWaveIn = 0;
	

	return 0;
}



};